# Copyright (c) 2017 Presto Labs Pte. Ltd.
# Author: jshin

import json

import google.protobuf.json_format as json_format

from coin.exchange.base.kr_rest.py_product import PyProduct
from coin.exchange.base.kr_rest.product import NativeProduct
from coin.exchange.base.kr_rest.futures_product import NativeFuturesProduct
from coin.exchange.base.tick import FixedTickPrice
from coin.exchange.base.tick_qty import FixedTickQty
from coin.proto.symbology_pb2 import ProductInfoProto, ProductInfoProtoBundle


# from exchange. must be enforced. None means no need to enforce.
# 1. price precision -> price formatter (float -> str) + tick size calculation?
# 2. qty precision -> qty formatter
# 3. min_qty (could be constant or based on quote currency)
# 4. max_qty
class ProductInfo(object):
  @staticmethod
  def gen1(price_precision, min_qty, max_qty):
    return ProductInfo(price_precision=price_precision, min_qty=min_qty, max_qty=max_qty)

  @staticmethod
  def gen2(price_precision, qty_precision):
    return ProductInfo(price_precision=price_precision, qty_precision=qty_precision)

  @staticmethod
  def gen3(price_ticksize, qty_precision):
    return ProductInfo(price_ticksize=price_ticksize, qty_precision=qty_precision)

  @staticmethod
  def gen4(price_precision, qty_precision, min_qty):
    return ProductInfo(price_precision=price_precision,
                       qty_precision=qty_precision,
                       min_qty=min_qty)

  def __init__(
      self,
      *,
      # price
      tick_price_cls=None,
      price_ticksize=None,
      price_precision=None,
      # qty
      qty_precision,
      qty_multiplier=None,
      min_qty=None,
      max_qty=None):
    # price
    price_modes = [
        tick_price_cls is not None,
        price_ticksize is not None,
        price_precision is not None,
    ]
    assert sum(price_modes) == 1, price_modes
    self._tick_price_cls = tick_price_cls
    self.price_ticksize = price_ticksize
    self.price_precision = price_precision

    # qty
    assert qty_precision is not None
    self.qty_precision = qty_precision
    self.qty_multiplier = qty_multiplier
    self.min_qty = min_qty
    self.max_qty = max_qty

  def gen_tick(self):
    return self.gen_tick_price()

  def gen_tick_price(self):
    if self._tick_price_cls is not None:
      return self._tick_price_cls()
    elif self.price_ticksize is not None:
      assert self.price_ticksize >= 0., self.price_ticksize
      return FixedTickPrice(self.price_ticksize)
    elif self.price_precision is not None:
      return FixedTickPrice(10**(-self.price_precision))
    else:
      raise ValueError("not supported")

  def gen_tick_qty(self):
    return FixedTickQty(qty_precision=self.qty_precision,
                        qty_multiplier=self.qty_multiplier,
                        min_qty=self.min_qty,
                        max_qty=self.max_qty)


class OrderFormatter(object):
  def __init__(self, product, tick_price, tick_qty):
    self.product = product
    self.tick_price = tick_price
    self.tick_qty = tick_qty

  def floor_price(self, price):
    return self.tick_price.floor(price)

  def ceil_price(self, price):
    return self.tick_price.ceil(price)

  def floor_qty(self, qty):
    return self.tick_qty.floor(qty)

  def ceil_qty(self, qty):
    return self.tick_qty.ceil(qty)

  def format_price(self, price):
    return self.tick_price.format(price)

  def format_qty(self, qty):
    return self.tick_qty.format(qty)


class ProductHolder(object):
  def __init__(self, product, product_info):
    assert isinstance(product, (NativeProduct, NativeFuturesProduct, PyProduct))
    assert isinstance(product_info, ProductInfoProto)
    self._product = product
    self._product_info = product_info
    self._tick_price = self._product_info.price_ticksize
    self._tick_qty = self._product_info.qty_ticksize

  def __str__(self):
    return "%s" % str(self._product)

  @property
  def base(self):
    return self._product.base

  @property
  def quote(self):
    return self._product.quote

  @property
  def symbol(self):
    return self._product.symbol

  @property
  def native_symbol(self):
    return self._product.native_symbol

  @property
  def product_info(self):
    return self._product_info

  def get_tick(self):
    return self._tick_price

  def get_tick_price(self):
    return self._tick_price

  def get_tick_qty(self):
    return self._tick_qty


class ProductInfoConstant(object):
  def __init__(self, filename):
    product_infos = ProductInfoProtoBundle()
    with open(filename) as f:
      constants = json.load(f)
      json_format.ParseDict(constants, product_infos, ignore_unknown_fields=True)
    self._product_infos = product_infos
    self._pi_map = {}
    self._pi_map_native = {}
    self._native_currencies = set()
    self._norm_currencies = set()
    self._native_products = set()
    for pi in self._product_infos.product_infos:
      self._pi_map[pi.symbol] = pi
      self._pi_map_native[pi.native_symbol] = pi
      self._native_currencies.add(pi.native_base)
      self._native_currencies.add(pi.native_quote)
      self._norm_currencies.add(pi.base)
      self._norm_currencies.add(pi.quote)
      self._native_products.add(pi.native_symbol)

  def get_product_info(self, symbol):
    return self._pi_map[symbol]

  def get_product_info_by_native_symbol(self, native_symbol):
    return self._pi_map[native_symbol]

  def has_native_product(self, native_symbol):
    return native_symbol in self._native_products

  def has_native_currency(self, native_currency):
    return native_currency in self._native_currencies

  def has_norm_currency(self, norm_currency):
    return norm_currency in self._norm_currencies
